Add ChangeDetectorAgent, test

New agent will watch the property of an inbound event stream. When that
property changes, it will emit the inbound event.

Michael Cramm 10 years ago
parent
commit
22bee753cd
2 changed files with 175 additions and 0 deletions
  1. 76 0
      app/models/agents/change_detector_agent.rb
  2. 99 0
      spec/models/agents/change_detector_agent_spec.rb

+ 76 - 0
app/models/agents/change_detector_agent.rb

@@ -0,0 +1,76 @@
1
+module Agents
2
+  class ChangeDetectorAgent < Agent
3
+    cannot_be_scheduled!
4
+
5
+    description <<-MD
6
+      The ChangeDetectorAgent receives a stream of events and emits a new event when a property of the received event changes.
7
+
8
+      `property` specifies the property to be watched.
9
+
10
+      `expected_update_period_in_days` is used to determine if the Agent is working.
11
+
12
+      The resulting event will be a copy of the received event.
13
+    MD
14
+
15
+    event_description <<-MD
16
+    This will change based on the source event. If you were event from the ShellCommandAgent, your outbound event might look like:
17
+
18
+      {
19
+        'command' => 'pwd',
20
+        'path' => '/home/Huginn',
21
+        'exit_status' => '0',
22
+        'errors' => '',
23
+        'output' => '/home/Huginn'
24
+      }
25
+    MD
26
+
27
+    def default_options
28
+      {
29
+          'property' => 'output',
30
+          'expected_update_period_in_days' => 1
31
+      }
32
+    end
33
+
34
+    def validate_options
35
+      unless options['property'].present? && options['expected_update_period_in_days'].present?
36
+        errors.add(:base, "The property and expected_update_period_in_days fields are all required.")
37
+      end
38
+    end
39
+
40
+    def working?
41
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
42
+    end
43
+
44
+    def receive(incoming_events)
45
+      incoming_events.each do |event|
46
+        handle(interpolated(event), event)
47
+      end
48
+    end
49
+
50
+    private
51
+
52
+    def handle(opts, event = nil)
53
+      property = opts['property']
54
+      if has_changed?(property)
55
+        created_event = create_event :payload => event.payload
56
+
57
+        log("Propagating new event as property has changed to #{property} from #{last_property}", :outbound_event => created_event, :inbound_event => event )
58
+        update_memory(property)
59
+      else
60
+        log("Not propagating as incoming event has not changed from #{last_property}.", :inbound_event => event )
61
+      end
62
+    end
63
+
64
+    def has_changed?(property)
65
+      property != last_property
66
+    end
67
+
68
+    def last_property
69
+      self.memory['last_property']
70
+    end
71
+
72
+    def update_memory(property)
73
+      self.memory['last_property'] = property
74
+    end
75
+  end
76
+end

+ 99 - 0
spec/models/agents/change_detector_agent_spec.rb

@@ -0,0 +1,99 @@
1
+require 'spec_helper'
2
+
3
+describe Agents::ChangeDetectorAgent do
4
+  def create_event(output=nil)
5
+    event = Event.new
6
+    event.agent = agents(:jane_weather_agent)
7
+    event.payload = {
8
+      :command => 'some-command',
9
+      :output => output
10
+    }
11
+    event.save!
12
+
13
+    event
14
+  end
15
+
16
+  before do
17
+    @valid_params = {
18
+        :property  => "{{output}}",
19
+        :expected_update_period_in_days => "1",
20
+      }
21
+
22
+    @checker = Agents::ChangeDetectorAgent.new(:name => "somename", :options => @valid_params)
23
+    @checker.user = users(:jane)
24
+    @checker.save!
25
+  end
26
+
27
+  describe "validation" do
28
+    before do
29
+      @checker.should be_valid
30
+    end
31
+
32
+    it "should validate presence of property" do
33
+      @checker.options[:property] = nil
34
+      @checker.should_not be_valid
35
+    end
36
+
37
+    it "should validate presence of property" do
38
+      @checker.options[:expected_update_period_in_days] = nil
39
+      @checker.should_not be_valid
40
+    end
41
+  end
42
+
43
+  describe "#working?" do
44
+    before :each do
45
+      # Need to create an event otherwise event_created_within? returns nil
46
+      event = create_event
47
+      @checker.receive([event])
48
+    end
49
+
50
+    it "is when event created within :expected_update_period_in_days" do
51
+      @checker.options[:expected_update_period_in_days] = 2
52
+      three_days_from_now = 1.days.from_now
53
+      @checker.should be_working
54
+    end
55
+
56
+    it "isnt when event created outside :expected_update_period_in_days" do
57
+      @checker.options[:expected_update_period_in_days] = 2
58
+      three_days_from_now = 2.days.from_now
59
+      stub(Time).now { three_days_from_now }
60
+
61
+      @checker.should_not be_working
62
+    end
63
+  end
64
+
65
+  describe "#receive" do
66
+    before :each do
67
+      @event = create_event("2014-07-01")
68
+    end
69
+
70
+    it "creates events when memory is empty" do
71
+      @event.payload[:output] = "2014-07-01"
72
+      expect {
73
+        @checker.receive([@event])
74
+      }.to change(Event, :count).by(1)
75
+      Event.last.payload[:command].should == @event.payload[:command]
76
+      Event.last.payload[:output].should == @event.payload[:output]
77
+    end
78
+
79
+    it "creates events when new event changed" do
80
+      @event.payload[:output] = "2014-07-01"
81
+      @checker.receive([@event])
82
+
83
+      event = create_event("2014-08-01")
84
+
85
+      expect {
86
+        @checker.receive([event])
87
+      }.to change(Event, :count).by(1)
88
+    end
89
+
90
+    it "does not create event when no change" do
91
+      @event.payload[:output] = "2014-07-01"
92
+      @checker.receive([@event])
93
+
94
+      expect {
95
+        @checker.receive([@event])
96
+      }.to change(Event, :count).by(0)
97
+    end
98
+  end
99
+end